package xfuzzy.xfghl.algorithm;

import java.text.SimpleDateFormat;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import xfuzzy.xfghl.codification.DoubleChromosome;
import xfuzzy.xfghl.cross.GPCrossover;
import xfuzzy.xfghl.evaluation.EvaluationXfuzzy;
import xfuzzy.xfghl.model.XfghlConfig;
import xfuzzy.xfghl.mutation.Mutation;
import xfuzzy.xfghl.output.GAOutput;
import xfuzzy.xfghl.population.Population;
import xfuzzy.xfghl.population.PopulationGenerator;
import xfuzzy.xfghl.population.RandomPopulationGenerator;
import xfuzzy.xfghl.replacement.GeneralizedCrowdingReplacement;
import xfuzzy.xfghl.replacement.Replacement;
import xfuzzy.xfghl.selection.Selection;
import xfuzzy.xfghl.selection.SelectionTournament;
import xfuzzy.xfghl.stop.NumeroEvaluaciones;
import xfuzzy.xfghl.stop.StopCondition;
import xfuzzy.xfghl.view.XfghlProperties;

/**
 * Clase que representa el algoritmo genetico
 * 
 * @author Alberto David Fernandez Fernandez
 * 
 * */

public class GeneticAlgorithm extends Observable implements Runnable, Observer {

	private PopulationGenerator populationGenerator;
	private Selection selection;
	private GPCrossover crossover;
	private Mutation mutation;
	private Replacement replacement;
	private StopCondition stopCondition;
	private DoubleChromosome best;
	private XfghlConfig configXfghl;
	private EvaluationXfuzzy evaluation;
	private List<Double> data;
	private ResultGA results;
	private boolean changeBest;
	private boolean finished;

	/**
	 * Constructor sin parametros de la clase
	 * */

	public GeneticAlgorithm() {

		this.populationGenerator = null;
		this.selection = null;
		this.crossover = null;
		this.mutation = null;
		this.replacement = null;
		this.stopCondition = null;
		this.best = null;
		this.configXfghl = null;
		this.evaluation = null;
		this.data = null;
		this.results = null;
		this.changeBest = false;
		this.finished = false;
	}

	/**
	 * Constructor de la clase
	 * 
	 * @param sel
	 *            Instancia para la seleccion de individuos a cruzar
	 * @param crossover
	 *            Instancia para el cruce de individuos
	 * @param mut
	 *            Instancia para la mutacion de individuos
	 * @param repl
	 *            Instancia para el reemplazo de los individuos en la poblacion
	 * @param popGen
	 *            Instancia para el generador de la poblacion inicial
	 * @param stopCond
	 *            Instancia para la condicion de parada
	 * @param eval
	 *            Instancia para la evaluacion de los individuos
	 * @param config
	 *            Instancia de la configuracion de la herramienta xfghl
	 * */

	public GeneticAlgorithm(Selection sel, GPCrossover crossover, Mutation mut, Replacement repl,
			PopulationGenerator popGen, StopCondition stopCond, EvaluationXfuzzy eval, XfghlConfig config) {

		this.selection = sel;
		this.crossover = crossover;
		this.mutation = mut;
		this.replacement = repl;
		this.populationGenerator = popGen;
		this.stopCondition = stopCond;
		this.configXfghl = config;
		this.evaluation = eval;
		this.best = null;
		this.data = null;
	}

	public Selection getSeleccion() {
		return selection;
	}

	public void setSeleccion(Selection seleccion) {
		this.selection = seleccion;
	}

	public GPCrossover getCruce() {
		return crossover;
	}

	public void setCruce(GPCrossover cruce) {
		this.crossover = cruce;
	}

	public Mutation getMutacion() {
		return mutation;
	}

	public void setMutacion(Mutation mutacion) {
		this.mutation = mutacion;
	}

	public EvaluationXfuzzy getEvaluacion() {
		return evaluation;
	}

	public void setEvaluacion(EvaluationXfuzzy evaluacion) {
		this.evaluation = evaluacion;
	}

	public Replacement getReemplazo() {
		return replacement;
	}

	public void setReemplazo(Replacement reemplazo) {
		this.replacement = reemplazo;
	}

	public DoubleChromosome getMejor() {
		return best;
	}

	public void setMejor(DoubleChromosome mejor) {
		this.best = mejor;
	}

	public PopulationGenerator getGeneradorPoblacion() {
		return populationGenerator;
	}

	public void setGeneradorPoblacion(PopulationGenerator generadorPoblacion) {
		this.populationGenerator = generadorPoblacion;
	}

	public StopCondition getParada() {
		return stopCondition;
	}

	public void setParada(StopCondition parada) {
		this.stopCondition = parada;
	}

	public XfghlConfig getConfig() {
		return configXfghl;
	}

	public void setConfig(XfghlConfig config) {
		this.configXfghl = config;
	}

	public void setData(List<Double> data) {
		this.data = data;
	}

	public List<Double> getData() {
		return this.data;
	}

	public DoubleChromosome getBest() {
		return this.best;
	}

	public void reset() {

		this.results = new ResultGA();
	}

	public ResultGA getResult() {
		return this.results;
	}

	public boolean isChangeBest() {
		return changeBest;
	}

	public void setChangeBest(boolean changeBest) {
		this.changeBest = changeBest;
	}

	public boolean isFinished() {
		return finished;
	}

	public void setFinished(boolean finished) {
		this.finished = finished;
	}

	/**
	 * Ejecucion del algoritmo genetico
	 * */

	public void run() {

		((EvaluationXfuzzy) evaluation).setObserver(this);
		String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(Calendar.getInstance().getTime());
		GAOutput.writeln(timeStamp, this.configXfghl.getLogFile());
		this.configXfghl.logProperties();
		this.reset();
		long startTime = System.currentTimeMillis();
		long intermediateTime = System.currentTimeMillis();
		this.data = new ArrayList<Double>();
		// crear poblacion inicial
		Population poblacion = this.populationGenerator.generarPoblacion(this.configXfghl);
		// evalua la poblacion
		this.evaluation.evaluarPoblacion(poblacion, configXfghl);
		// busca al mejor de la poblacion
		this.best = poblacion.getBest();
		GAOutput.writeln("Mejor inicial: " + this.best.getCodificacion() + ". Fitness: " + this.best.getFitness(),
				this.configXfghl.getLogFile());
		this.changeBest = true;
		results.setBest(this.best.getFitness());
		setChanged();
		notifyObservers();
		clearChanged();
		List<DoubleChromosome> seleccionados = null;
		DoubleChromosome child1 = null, child2 = null, father1 = null, father2 = null;
		List<DoubleChromosome> offspring = null;
		while (stopCondition.parar(this.results) == false) {
			GAOutput.writeln("Iteracion " + (this.results.getNumGenerations() + 1), this.configXfghl.getLogFile());
			this.changeBest = false;
			// selecciona individuos para el cruce
			seleccionados = this.selection.seleccionar(poblacion);
			father1 = seleccionados.get(0);
			father2 = seleccionados.get(1);
			GAOutput.writeln("Padre 1: " + father1.getCodificacion() + ". Fitness: " + father1.getFitness(),
					this.configXfghl.getLogFile());
			GAOutput.writeln("Padre 2: " + father2.getCodificacion() + ". Fitness: " + father2.getFitness(),
					this.configXfghl.getLogFile());
			// cruza los individuos
			offspring = this.crossover.cruzar(father1, father2, configXfghl);
			child1 = offspring.get(0);
			child2 = offspring.get(1);
			// muta los individuos
			this.mutation.mutar(child1, child2, configXfghl);
			// evalua los nuevos individuos
			this.evaluation.evaluarIndividuos(child1, child2, configXfghl);
			GAOutput.writeln("Hijo 1: " + child1.getCodificacion() + ". Fitness: " + child1.getFitness(),
					this.configXfghl.getLogFile());
			GAOutput.writeln("Hijo 2: " + child2.getCodificacion() + ". Fitness: " + child2.getFitness(),
					this.configXfghl.getLogFile());
			// reemplaza los nuevos individuos
			this.replacement.replace(poblacion, child1, child2, father1, father2);
			// comprueba si es mejor
			if (child1.getFitness() < this.best.getFitness()) {
				this.best = child1;
				this.results.setBest(child1.getFitness());
				this.changeBest = true;
				GAOutput.writeln("Mejora: " + child1.getCodificacion() + ". Fitness: " + child1.getFitness(),
						this.configXfghl.getLogFile());
			}
			if (child2.getFitness() < this.best.getFitness()) {
				this.best = child2;
				this.results.setBest(child2.getFitness());
				this.changeBest = true;
				GAOutput.writeln("Mejora: " + child2.getCodificacion() + ". Fitness: " + child2.getFitness(),
						this.configXfghl.getLogFile());
			}
			// aumenta el numero de generaciones
			this.results.increaseNumIterations();
			// guarda el mejor en esta iteracion
			this.data.add(best.getFitness());
			intermediateTime = System.currentTimeMillis();
			this.results.setTime(intermediateTime - startTime);
			GAOutput.writeln("Mejor: " + best.getCodificacion() + ". Fitness: " + best.getFitness(),
					this.configXfghl.getLogFile());
			setChanged();
			notifyObservers();
			clearChanged();
		}

		long finishTime = System.currentTimeMillis();
		long time = finishTime - startTime;
		this.results.setTime(time);
		GAOutput.writeln("Fin de ejecucion", this.configXfghl.getLogFile());
		GAOutput.writeln("Iteraciones: " + this.results.getNumGenerations(), this.configXfghl.getLogFile());
		GAOutput.writeln("Evaluaciones: " + this.results.getNumEvaluations(), this.configXfghl.getLogFile());
		GAOutput.writeln("Tiempo de ejecucion: " + this.results.getTime(), this.configXfghl.getLogFile());
		GAOutput.writeln("Mejor: " + best.getCodificacion() + ". Fitness: " + best.getFitness(),
				this.configXfghl.getLogFile());
		this.finished = true;
	}

	/**
	 * Metodo que configura el algoritmo
	 * 
	 * @param config
	 *            Instancia de la configuracion de la herramienta xfghl
	 * */

	public void configure(XfghlConfig config) {

		if (config.getSelectionSeed() != 0)
			this.selection = new SelectionTournament(config.getTournamentSize(), config.getSelectionSeed());
		else
			this.selection = new SelectionTournament(config.getTournamentSize(), System.nanoTime());
		if (config.getCrossoverSeed() != 0)
			this.crossover = new GPCrossover(config.getCrossoverSeed());
		else
			this.crossover = new GPCrossover(System.nanoTime());
		if (config.getMutationSeed() != 0)
			this.mutation = new Mutation(config.getMutationSeed());
		else
			this.mutation = new Mutation(System.nanoTime());
		if (config.getReplacementSeed() != 0)
			this.replacement = new GeneralizedCrowdingReplacement(config.getScalingFactor(),
					config.getReplacementSeed());
		else
			this.replacement = new GeneralizedCrowdingReplacement(config.getScalingFactor(), System.nanoTime());
		if (config.getPopopulationSeed() != 0)
			this.populationGenerator = new RandomPopulationGenerator(config.getPopopulationSeed(),
					config.getPopulationSize());
		else
			this.populationGenerator = new RandomPopulationGenerator(config.getPopopulationSeed(),
					config.getPopulationSize());
		this.stopCondition = null;
		if (config.getStopConditionGA() == XfghlProperties.stopConditionEvaluationsValue) {
			this.stopCondition = new NumeroEvaluaciones(config.getValueStopConditionGA());
		}
		this.evaluation = new EvaluationXfuzzy();
		this.configXfghl = config;
	}

	/**
	 * Metodo que actualiza datos desde los hilos
	 * 
	 * @param o
	 *            Objeto observado
	 * @param arg
	 *            Parametro del objeto observado
	 * */

	@Override
	public synchronized void update(Observable o, Object arg) {

		// Cada ejecucion de este metodo es una nueva evaluacion
		// con lo que se podria incrementar las evaluaciones aqui
		// y quitarlas de parametro a los hilos
		this.results.increaseNumEvaluations(1);

		if (arg instanceof DoubleChromosome) {
			DoubleChromosome chromosome = (DoubleChromosome) arg;
			if (this.best != null) {
				if (chromosome.getFitness() < this.best.getFitness()) {
					this.best = chromosome;
					this.changeBest = true;
				}
			} else {
				this.best = chromosome;
				this.changeBest = true;
			}
			setChanged();
			notifyObservers();
			clearChanged();
			this.changeBest = false;
		}
	}
}
